package org.finesys.common.security.client.service.impl;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import feign.template.UriUtils;
import lombok.RequiredArgsConstructor;
import org.finesys.common.constants.BaseConstant;
import org.finesys.common.core.constants.ResultCode;
import org.finesys.common.core.exception.BusinessException;
import org.finesys.common.core.module.ROpt;
import org.finesys.common.security.authentication.gitee.GiteeTokenResponse;
import org.finesys.common.security.authentication.gitee.GiteeUserInfoResponse;
import org.finesys.common.security.client.exception.RedirectUriGiteeException;
import org.finesys.common.security.client.properties.GiteeProperties;
import org.finesys.common.security.client.service.GiteeRegisteredClientRepository;
import org.finesys.common.security.core.module.AuthUser;
import org.finesys.common.security.core.util.OAuth2EndpointUtils;
import org.finesys.system.api.dto.SysUserDTO;
import org.finesys.system.api.dto.UserGiteeDto;
import org.finesys.system.api.service.UserGiteeService;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.finesys.common.security.client.core.constants.GiteeEndpointConstants.USER_INFO_URL;

@RequiredArgsConstructor
public class DefaultGiteeRegistererClientRepository implements GiteeRegisteredClientRepository {


    private final GiteeProperties giteeProperties;
    private final StringRedisTemplate cacheService;
    private final UserGiteeService userGiteeService;

    private static final String GITEE_STATE_PREFIX = "gitee_state_prefix";
    private static final String GITEE_USERS_PREFIX = "gitee_users_prefix";

    @Override
    public String getRedirectURIByAppId(String appId) {
        Assert.hasText(appId, "appId不能为空");
        GiteeProperties.Gitee gitee = getGiteeByAppId(appId);
        String redirectURIPrefix = gitee.getRedirectUriPrefix();
        if (StringUtils.hasText(redirectURIPrefix)) {
            return UriUtils.encode(redirectURIPrefix + "/" + appId, StandardCharsets.UTF_8);
        }
        OAuth2Error oAuth2Error = new OAuth2Error(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), "重定向地址前缀不能为空", "");
        throw new RedirectUriGiteeException(oAuth2Error);
    }

    @Override
    public String stateGenerate(String appId) {
        String state = UUID.randomUUID().toString();
        cacheService.opsForValue().set(GITEE_STATE_PREFIX + StrPool.COLON + appId + StrPool.COLON + state, state, 30, TimeUnit.SECONDS);
        return state;
    }

    @Override
    public void storeBinding(Long id, String appId, Long userId) {
        UserGiteeDto userGiteeDto = new UserGiteeDto();
        userGiteeDto.setId(id);
        userGiteeDto.setAppId(appId);
        userGiteeDto.setUserId(userId);
        userGiteeService.binding(userGiteeDto);
    }

    @Override
    public GiteeTokenResponse getAccessTokenResponse(String appId, String code, String accessTokenUrl) {
        GiteeProperties.Gitee gitee = getGiteeByAppId(appId);
        Map<String, String> uriVariables = new HashMap<>(8);
        uriVariables.put(OAuth2ParameterNames.CLIENT_ID, gitee.getClientId());
        uriVariables.put(OAuth2ParameterNames.CODE, code);
        uriVariables.put(OAuth2ParameterNames.CLIENT_SECRET, gitee.getClientSecret());
        uriVariables.put(OAuth2ParameterNames.REDIRECT_URI, gitee.getRedirectUriPrefix() + "/" + appId);

        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        //将请求放入HttpEntity
        HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(headers);
        GiteeTokenResponse giteeTokenResponse = null;
        try {
            giteeTokenResponse = restTemplate.postForObject(accessTokenUrl, httpEntity, GiteeTokenResponse.class, uriVariables);
        } catch (RestClientException e) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }
        if (Objects.isNull(giteeTokenResponse)) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }
        if (giteeTokenResponse.getAccessToken() == null) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
                    OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
        }
        return giteeTokenResponse;
    }

    @Override
    public GiteeUserInfoResponse getUserInfo(String userinfoUrl, String appId, String state, GiteeTokenResponse giteeTokenResponse) {
        RestTemplate restTemplate = new RestTemplate();
        String accessToken = giteeTokenResponse.getAccessToken();
        GiteeUserInfoResponse giteeUserInfoResponse = null;
        try {
            giteeUserInfoResponse = restTemplate.getForObject(userinfoUrl + "?" + OAuth2ParameterNames.ACCESS_TOKEN + "=" + accessToken, GiteeUserInfoResponse.class);
        } catch (RestClientException e) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", USER_INFO_URL);
        }
        if (Objects.isNull(giteeUserInfoResponse)) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", USER_INFO_URL);
        }
        Long id = giteeUserInfoResponse.getId();
        if (!Objects.isNull(id)) {
            OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "", USER_INFO_URL);
        }
        return giteeUserInfoResponse;
    }

    @Override
    public void storeGiteeUsers(GiteeTokenResponse giteeTokenResponse, GiteeUserInfoResponse giteeUserInfoResponse, String appId) {
        String accessToken = giteeTokenResponse.getAccessToken();
        String refreshToken = giteeTokenResponse.getRefreshToken();
        String scope = giteeTokenResponse.getScope();
        Integer expiredIn = giteeTokenResponse.getExpiresIn();
        LocalDateTime expires = LocalDateTime.now().plusSeconds(expiredIn);
        Long id = giteeUserInfoResponse.getId();
        String avatarUrl = giteeUserInfoResponse.getAvatarUrl();
        UserGiteeDto userGiteeDto = ROpt.ofNullable(userGiteeService.getByAppIdAndId(appId, id))
                .assertSuccess(r -> new BusinessException("查询接口失败"))
                .peek()
                .getData();
        if (Objects.isNull(userGiteeDto)) {
            UserGiteeDto giteeDto = new UserGiteeDto();
            giteeDto.setAppId(appId);
            giteeDto.setId(id);
            giteeDto.setAccessToken(accessToken);
            giteeDto.setRefreshToken(refreshToken);
            giteeDto.setExpires(expires);
            giteeDto.setScope(scope);
            userGiteeService.add(giteeDto);
        } else {
            userGiteeDto.setAccessToken(accessToken);
            userGiteeDto.setRefreshToken(refreshToken);
            userGiteeDto.setExpires(expires);
            userGiteeDto.setScope(scope);
            userGiteeDto.setAvatarUrl(avatarUrl);
            userGiteeService.update(userGiteeDto);
        }
    }

    @Override
    public void tagUsers(String appId, String state, Long userId) {
        cacheService.opsForValue().set(GITEE_USERS_PREFIX + StrPool.COLON + appId + StrPool.COLON + state, String.valueOf(userId), 30, TimeUnit.SECONDS);
    }

    @Override
    public String getTagUser(String appId, String state) {
        return cacheService.opsForValue().get(GITEE_USERS_PREFIX + StrPool.COLON + appId + StrPool.COLON + state);
    }

    @Override
    public UserDetails getUser(String appId, Long id) {
        SysUserDTO sysUser = ROpt.ofNullable(userGiteeService.getUser(appId, id))
                .assertSuccess(r -> new BusinessException("查询接口失败"))
                .peek().getData();

        if (Objects.isNull(sysUser)) {
            OAuth2Error oAuth2Error = new OAuth2Error(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), "用户未绑定，请先绑定", "");
            throw new OAuth2AuthenticationException(oAuth2Error);
        }
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = StrUtil.equals(sysUser.getStatus(), BaseConstant.STATUS_VALID);
        List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(sysUser.getPerms());
        AuthUser authUser = new AuthUser(sysUser.getUsername(), sysUser.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
        BeanUtils.copyProperties(sysUser, authUser);
        authUser.setAppId(appId);
        return authUser;
    }

    @Override
    public GiteeProperties.Gitee getGiteeByAppId(String appId) {
        Assert.hasText(appId, "appId不能为空");
        List<GiteeProperties.Gitee> clients = giteeProperties.getList();
        if (CollectionUtils.isEmpty(clients)) {
            return null;
        }
        Optional<GiteeProperties.Gitee> optional = clients.stream().filter(c -> appId.equals(c.getAppId())).findFirst();
        if (optional.isPresent()) {
            return optional.get();
        }
        OAuth2Error oAuth2Error = new OAuth2Error(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), "未配置AppId", "");
        throw new OAuth2AuthenticationException(oAuth2Error);
    }

    @Override
    public boolean stateValid(String appId, String code, String state) {
        Assert.hasText(appId, "appId不能为空");
        Assert.hasText(code, "授权码不能为空");
        Assert.hasText(state, "状态码不能为空");
        String cacheState = cacheService.opsForValue().get(GITEE_STATE_PREFIX + StrPool.COLON + appId + StrPool.COLON + state);
        Assert.hasText(cacheState, "状态码校验失败");
        if (cacheState == null) {
            return false;
        }
        return cacheState.equals(state);
    }
}
